/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.foundation

import android.content.Context
import android.view.ViewConfiguration
import androidx.compose.foundation.gestures.AndroidConfig
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Stable
import androidx.compose.testutils.assertIsEqualTo
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.MouseInjectionScope
import androidx.compose.ui.test.ScrollWheel
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.getUnclippedBoundsInRoot
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeWithVelocity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.test.StandardTestDispatcher
import org.junit.Rule

open class BaseLazyLayoutTestWithOrientation(private val orientation: Orientation) {
    @get:Rule val rule = createComposeRule(StandardTestDispatcher())

    val vertical: Boolean
        get() = orientation == Orientation.Vertical

    @Stable
    fun Modifier.crossAxisSize(size: Dp) =
        if (vertical) {
            this.width(size)
        } else {
            this.height(size)
        }

    @Stable
    fun Modifier.mainAxisSize(size: Dp) =
        if (vertical) {
            this.height(size)
        } else {
            this.width(size)
        }

    @Stable
    fun Modifier.axisSize(crossAxis: Dp, mainAxis: Dp) =
        if (vertical) {
            this.size(crossAxis, mainAxis)
        } else {
            this.size(mainAxis, crossAxis)
        }

    fun SemanticsNodeInteraction.scrollBy(x: Dp = 0.dp, y: Dp = 0.dp, density: Density) =
        performTouchInput {
            with(density) {
                val touchSlop = TestTouchSlop.toInt()
                val xPx = x.roundToPx()
                val yPx = y.roundToPx()
                val offsetX = if (xPx > 0) xPx + touchSlop else if (xPx < 0) xPx - touchSlop else 0
                val offsetY = if (yPx > 0) yPx + touchSlop else if (yPx < 0) yPx - touchSlop else 0
                swipeWithVelocity(
                    start = center,
                    end = Offset(center.x - offsetX, center.y - offsetY),
                    endVelocity = 0f,
                )
            }
        }

    fun SemanticsNodeInteraction.scrollMainAxisBy(distance: Dp) {
        if (vertical) {
            this.scrollBy(y = distance, density = rule.density)
        } else {
            this.scrollBy(x = distance, density = rule.density)
        }
    }

    fun SemanticsNodeInteraction.assertMainAxisSizeIsEqualTo(expectedSize: Dp) =
        if (vertical) {
            assertHeightIsEqualTo(expectedSize)
        } else {
            assertWidthIsEqualTo(expectedSize)
        }

    fun SemanticsNodeInteraction.assertCrossAxisSizeIsEqualTo(expectedSize: Dp) =
        if (vertical) {
            assertWidthIsEqualTo(expectedSize)
        } else {
            assertHeightIsEqualTo(expectedSize)
        }

    fun SemanticsNodeInteraction.assertStartPositionIsAlmost(expected: Dp) {
        val position =
            if (vertical) {
                getUnclippedBoundsInRoot().top
            } else {
                getUnclippedBoundsInRoot().left
            }
        position.assertIsEqualTo(expected, tolerance = 1.dp)
    }

    fun SemanticsNodeInteraction.assertMainAxisStartPositionInRootIsEqualTo(expectedStart: Dp) =
        if (vertical) {
            assertTopPositionInRootIsEqualTo(expectedStart)
        } else {
            assertLeftPositionInRootIsEqualTo(expectedStart)
        }

    fun SemanticsNodeInteraction.assertStartPositionInRootIsEqualTo(expectedStart: Dp) =
        if (vertical) {
            assertTopPositionInRootIsEqualTo(expectedStart)
        } else {
            assertLeftPositionInRootIsEqualTo(expectedStart)
        }

    fun SemanticsNodeInteraction.assertCrossAxisStartPositionInRootIsEqualTo(expectedStart: Dp) =
        if (vertical) {
            assertLeftPositionInRootIsEqualTo(expectedStart)
        } else {
            assertTopPositionInRootIsEqualTo(expectedStart)
        }

    fun SemanticsNodeInteraction.assertAxisBounds(offset: DpOffset, size: DpSize) =
        assertMainAxisStartPositionInRootIsEqualTo(offset.y)
            .assertCrossAxisStartPositionInRootIsEqualTo(offset.x)
            .assertMainAxisSizeIsEqualTo(size.height)
            .assertCrossAxisSizeIsEqualTo(size.width)

    fun PaddingValues(mainAxis: Dp = 0.dp, crossAxis: Dp = 0.dp) =
        PaddingValues(
            beforeContent = mainAxis,
            afterContent = mainAxis,
            beforeContentCrossAxis = crossAxis,
            afterContentCrossAxis = crossAxis,
        )

    fun PaddingValues(
        beforeContent: Dp = 0.dp,
        afterContent: Dp = 0.dp,
        beforeContentCrossAxis: Dp = 0.dp,
        afterContentCrossAxis: Dp = 0.dp,
    ) =
        if (vertical) {
            androidx.compose.foundation.layout.PaddingValues(
                start = beforeContentCrossAxis,
                top = beforeContent,
                end = afterContentCrossAxis,
                bottom = afterContent,
            )
        } else {
            androidx.compose.foundation.layout.PaddingValues(
                start = beforeContent,
                top = beforeContentCrossAxis,
                end = afterContent,
                bottom = afterContentCrossAxis,
            )
        }

    fun MouseInjectionScope.mouseWheelScrollAcrossMainAxis(context: Context, deltaPx: Float) {
        with(AndroidConfig(ViewConfiguration.get(context))) {
            if (vertical) {
                scroll(deltaPx / getVerticalScrollFactor(), ScrollWheel.Vertical)
            } else {
                scroll(deltaPx / getHorizontalScrollFactor(), ScrollWheel.Horizontal)
            }
        }
    }

    internal fun Modifier.debugBorder(color: Color = Color.Black) = border(1.dp, color)

    companion object {
        internal const val FrameDuration = 16L
    }
}
